package simple.utils.bitmaps;

import com.googlecode.javaewah.EWAHCompressedBitmap;

import java.io.*;
import java.util.BitSet;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created with IntelliJ IDEA.
 * User: rehak
 * Date: 9/5/13
 * Time: 3:20 PM
 * To change this template use File | Settings | File Templates.
 */
public class BitmapEWAH implements Bitmap {

    private static final int MAX_CACHE_SIZE = 10000;
    private final BitSet setCache = new BitSet();
    private final EWAHCompressedBitmap bs;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public BitmapEWAH() {
        bs = new EWAHCompressedBitmap();
    }

    public BitmapEWAH(EWAHCompressedBitmap backingSet) {
        this.bs = backingSet;
    }

    public BitmapEWAH(int[] set) {
        this(EWAHCompressedBitmap.bitmapOf(set));
    }

    private void flush() {
        lock.writeLock().lock();
        try {
            int[] ints = bs.toArray();
            for (int i = 0; i < ints.length; i++) {
                setCache.set(ints[i]);
            }
            bs.clear();
            for (int i = 0; i < setCache.length(); i++) {
                if (setCache.get(i)) {
                    bs.set(i);
                }
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    @Override
    public void set(int position) {
        lock.writeLock().lock();
        try {
            // try to set
            if (!bs.set(position)) {
                // if fails, must set smartly

                EWAHCompressedBitmap setMap = EWAHCompressedBitmap.bitmapOf(position);
                EWAHCompressedBitmap newMap = bs.or(setMap);
                bs.clear();
                int[] ints = newMap.toArray();
                for (int i = 0; i < ints.length; i++) {
                    bs.set(ints[i]);
                }
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    @Override
    public boolean get(int position) {
        lock.readLock().lock();
        try {
            return bs.get(position);
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public Bitmap readOnly() {
        return new BitmapEWAH(this.bs) {
            @Override
            public void set(int position) {
                throw new UnsupportedOperationException("Not supported!");
            }
        };
    }

    @Override
    public Bitmap and(Bitmap other) {
        if (other instanceof BitmapEWAH) {
            BitmapEWAH o1 = (BitmapEWAH) other;
            return new BitmapEWAH(o1.bs.and(bs));
        } else {
            int[] os = other.toArray();
            EWAHCompressedBitmap oc = EWAHCompressedBitmap.bitmapOf(os);
            return new BitmapEWAH(bs.and(oc));
        }
    }

    @Override
    public int length() {
        return bs.sizeInBits();
    }

    @Override
    public int cardinality() {
        return bs.cardinality();
    }

    @Override
    public byte[] toUncompressedByteArray() {
        int[] sets = bs.toArray();
        BitSet bs = new BitSet();
        for (int i = 0; i < sets.length; i++) {
            bs.set(sets[i]);
        }
        return bs.toByteArray();
    }

    public NavigableSet<Integer> toSet() {
        lock.readLock().lock();
        try {
            return new TreeSet<>(bs.getPositions());
        } finally {
            lock.readLock().unlock();
        }
    }

    public BitmapEWAH andNot(BitmapEWAH m) {
        lock.readLock().lock();
        try {
            return new BitmapEWAH(bs.andNot(m.bs));
        } finally {
            lock.readLock().unlock();
        }
    }

    public BitmapEWAH xor(BitmapEWAH m) {
        lock.readLock().lock();
        try {
            return new BitmapEWAH(bs.xor(m.bs));
        } finally {
            lock.readLock().unlock();
        }
    }

    public BitmapEWAH or(BitmapEWAH m) {
        lock.readLock().lock();
        try {
            return new BitmapEWAH(bs.or(m.bs));
        } finally {
            lock.readLock().unlock();
        }
    }

    public BitmapEWAH and(BitmapEWAH m) {
        lock.readLock().lock();
        try {
            return new BitmapEWAH(bs.and(m.bs));
        } finally {
            lock.readLock().unlock();
        }
    }

    public static BitmapEWAH fromBytes(byte[] bytes, int concurrency) throws IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream oi = new ObjectInputStream(in);
        EWAHCompressedBitmap b = new EWAHCompressedBitmap();
        b.readExternal(oi);
        oi.close();
        return new BitmapEWAH(b);
    }

    @Override
    public byte[] toBytes() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bos);
        bs.writeExternal(oo);
        oo.flush();
        oo.close();
        return bos.toByteArray();
    }

    @Override
    public String toString() {
        return bs.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BitmapEWAH bitmap = (BitmapEWAH) o;

        if (bs != null ? !bs.equals(bitmap.bs) : bitmap.bs != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return bs != null ? bs.hashCode() : 0;
    }

    @Override
    public int[] toArray() {
        lock.readLock().lock();
        try {
            return bs.toArray();
        } finally {
            lock.readLock().unlock();
        }
    }

}